/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.awt.dnd;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.datatransfer.FlavorMap;
import java.awt.datatransfer.SystemFlavorMap;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.peer.DragSourceContextPeer;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.AccessController;
import java.util.EventListener;
import sun.awt.dnd.SunDragSourceContextPeer;
import sun.security.action.GetIntegerAction;


/**
 * The <code>DragSource</code> is the entity responsible
 * for the initiation of the Drag
 * and Drop operation, and may be used in a number of scenarios:
 * <UL>
 * <LI>1 default instance per JVM for the lifetime of that JVM.
 * <LI>1 instance per class of potential Drag Initiator object (e.g
 * TextField). [implementation dependent]
 * <LI>1 per instance of a particular
 * <code>Component</code>, or application specific
 * object associated with a <code>Component</code>
 * instance in the GUI. [implementation dependent]
 * <LI>Some other arbitrary association. [implementation dependent]
 * </UL>
 *
 * Once the <code>DragSource</code> is
 * obtained, a <code>DragGestureRecognizer</code> should
 * also be obtained to associate the <code>DragSource</code>
 * with a particular
 * <code>Component</code>.
 * <P>
 * The initial interpretation of the user's gesture,
 * and the subsequent starting of the drag operation
 * are the responsibility of the implementing
 * <code>Component</code>, which is usually
 * implemented by a <code>DragGestureRecognizer</code>.
 * <P>
 * When a drag gesture occurs, the
 * <code>DragSource</code>'s
 * startDrag() method shall be
 * invoked in order to cause processing
 * of the user's navigational
 * gestures and delivery of Drag and Drop
 * protocol notifications. A
 * <code>DragSource</code> shall only
 * permit a single Drag and Drop operation to be
 * current at any one time, and shall
 * reject any further startDrag() requests
 * by throwing an <code>IllegalDnDOperationException</code>
 * until such time as the extant operation is complete.
 * <P>
 * The startDrag() method invokes the
 * createDragSourceContext() method to
 * instantiate an appropriate
 * <code>DragSourceContext</code>
 * and associate the <code>DragSourceContextPeer</code>
 * with that.
 * <P>
 * If the Drag and Drop System is
 * unable to initiate a drag operation for
 * some reason, the startDrag() method throws
 * a <code>java.awt.dnd.InvalidDnDOperationException</code>
 * to signal such a condition. Typically this
 * exception is thrown when the underlying platform
 * system is either not in a state to
 * initiate a drag, or the parameters specified are invalid.
 * <P>
 * Note that during the drag, the
 * set of operations exposed by the source
 * at the start of the drag operation may not change
 * until the operation is complete.
 * The operation(s) are constant for the
 * duration of the operation with respect to the
 * <code>DragSource</code>.
 *
 * @since 1.2
 */

public class DragSource implements Serializable {

  private static final long serialVersionUID = 6236096958971414066L;

    /*
     * load a system default cursor
     */

  private static Cursor load(String name) {
    if (GraphicsEnvironment.isHeadless()) {
      return null;
    }

    try {
      return (Cursor) Toolkit.getDefaultToolkit().getDesktopProperty(name);
    } catch (Exception e) {
      e.printStackTrace();

      throw new RuntimeException("failed to load system cursor: " + name + " : " + e.getMessage());
    }
  }


  /**
   * The default <code>Cursor</code> to use with a copy operation indicating
   * that a drop is currently allowed. <code>null</code> if
   * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
   *
   * @see java.awt.GraphicsEnvironment#isHeadless
   */
  public static final Cursor DefaultCopyDrop =
      load("DnD.Cursor.CopyDrop");

  /**
   * The default <code>Cursor</code> to use with a move operation indicating
   * that a drop is currently allowed. <code>null</code> if
   * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
   *
   * @see java.awt.GraphicsEnvironment#isHeadless
   */
  public static final Cursor DefaultMoveDrop =
      load("DnD.Cursor.MoveDrop");

  /**
   * The default <code>Cursor</code> to use with a link operation indicating
   * that a drop is currently allowed. <code>null</code> if
   * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
   *
   * @see java.awt.GraphicsEnvironment#isHeadless
   */
  public static final Cursor DefaultLinkDrop =
      load("DnD.Cursor.LinkDrop");

  /**
   * The default <code>Cursor</code> to use with a copy operation indicating
   * that a drop is currently not allowed. <code>null</code> if
   * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
   *
   * @see java.awt.GraphicsEnvironment#isHeadless
   */
  public static final Cursor DefaultCopyNoDrop =
      load("DnD.Cursor.CopyNoDrop");

  /**
   * The default <code>Cursor</code> to use with a move operation indicating
   * that a drop is currently not allowed. <code>null</code> if
   * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
   *
   * @see java.awt.GraphicsEnvironment#isHeadless
   */
  public static final Cursor DefaultMoveNoDrop =
      load("DnD.Cursor.MoveNoDrop");

  /**
   * The default <code>Cursor</code> to use with a link operation indicating
   * that a drop is currently not allowed. <code>null</code> if
   * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
   *
   * @see java.awt.GraphicsEnvironment#isHeadless
   */
  public static final Cursor DefaultLinkNoDrop =
      load("DnD.Cursor.LinkNoDrop");

  private static final DragSource dflt =
      (GraphicsEnvironment.isHeadless()) ? null : new DragSource();

  /**
   * Internal constants for serialization.
   */
  static final String dragSourceListenerK = "dragSourceL";
  static final String dragSourceMotionListenerK = "dragSourceMotionL";

  /**
   * Gets the <code>DragSource</code> object associated with
   * the underlying platform.
   *
   * @return the platform DragSource
   * @throws HeadlessException if GraphicsEnvironment.isHeadless() returns true
   * @see java.awt.GraphicsEnvironment#isHeadless
   */
  public static DragSource getDefaultDragSource() {
    if (GraphicsEnvironment.isHeadless()) {
      throw new HeadlessException();
    } else {
      return dflt;
    }
  }

  /**
   * Reports
   * whether or not drag
   * <code>Image</code> support
   * is available on the underlying platform.
   * <P>
   *
   * @return if the Drag Image support is available on this platform
   */

  public static boolean isDragImageSupported() {
    Toolkit t = Toolkit.getDefaultToolkit();

    Boolean supported;

    try {
      supported = (Boolean) Toolkit.getDefaultToolkit()
          .getDesktopProperty("DnD.isDragImageSupported");

      return supported.booleanValue();
    } catch (Exception e) {
      return false;
    }
  }

  /**
   * Creates a new <code>DragSource</code>.
   *
   * @throws HeadlessException if GraphicsEnvironment.isHeadless() returns true
   * @see java.awt.GraphicsEnvironment#isHeadless
   */
  public DragSource() throws HeadlessException {
    if (GraphicsEnvironment.isHeadless()) {
      throw new HeadlessException();
    }
  }

  /**
   * Start a drag, given the <code>DragGestureEvent</code>
   * that initiated the drag, the initial
   * <code>Cursor</code> to use,
   * the <code>Image</code> to drag,
   * the offset of the <code>Image</code> origin
   * from the hotspot of the <code>Cursor</code> at
   * the instant of the trigger,
   * the <code>Transferable</code> subject data
   * of the drag, the <code>DragSourceListener</code>,
   * and the <code>FlavorMap</code>.
   * <P>
   *
   * @param trigger the <code>DragGestureEvent</code> that initiated the drag
   * @param dragCursor the initial {@code Cursor} for this drag operation or {@code null} for the
   * default cursor handling; see <a href="DragSourceContext.html#defaultCursor">DragSourceContext</a>
   * for more details on the cursor handling mechanism during drag and drop
   * @param dragImage the image to drag or {@code null}
   * @param imageOffset the offset of the <code>Image</code> origin from the hotspot of the
   * <code>Cursor</code> at the instant of the trigger
   * @param transferable the subject data of the drag
   * @param dsl the <code>DragSourceListener</code>
   * @param flavorMap the <code>FlavorMap</code> to use, or <code>null</code> <P>
   * @throws java.awt.dnd.InvalidDnDOperationException if the Drag and Drop system is unable to
   * initiate a drag operation, or if the user attempts to start a drag while an existing drag
   * operation is still executing
   */

  public void startDrag(DragGestureEvent trigger,
      Cursor dragCursor,
      Image dragImage,
      Point imageOffset,
      Transferable transferable,
      DragSourceListener dsl,
      FlavorMap flavorMap) throws InvalidDnDOperationException {

    SunDragSourceContextPeer.setDragDropInProgress(true);

    try {
      if (flavorMap != null) {
        this.flavorMap = flavorMap;
      }

      DragSourceContextPeer dscp = Toolkit.getDefaultToolkit().createDragSourceContextPeer(trigger);

      DragSourceContext dsc = createDragSourceContext(dscp,
          trigger,
          dragCursor,
          dragImage,
          imageOffset,
          transferable,
          dsl
      );

      if (dsc == null) {
        throw new InvalidDnDOperationException();
      }

      dscp.startDrag(dsc, dsc.getCursor(), dragImage, imageOffset); // may throw
    } catch (RuntimeException e) {
      SunDragSourceContextPeer.setDragDropInProgress(false);
      throw e;
    }
  }

  /**
   * Start a drag, given the <code>DragGestureEvent</code>
   * that initiated the drag, the initial
   * <code>Cursor</code> to use,
   * the <code>Transferable</code> subject data
   * of the drag, the <code>DragSourceListener</code>,
   * and the <code>FlavorMap</code>.
   * <P>
   *
   * @param trigger the <code>DragGestureEvent</code> that initiated the drag
   * @param dragCursor the initial {@code Cursor} for this drag operation or {@code null} for the
   * default cursor handling; see <a href="DragSourceContext.html#defaultCursor">DragSourceContext</a>
   * for more details on the cursor handling mechanism during drag and drop
   * @param transferable the subject data of the drag
   * @param dsl the <code>DragSourceListener</code>
   * @param flavorMap the <code>FlavorMap</code> to use or <code>null</code> <P>
   * @throws java.awt.dnd.InvalidDnDOperationException if the Drag and Drop system is unable to
   * initiate a drag operation, or if the user attempts to start a drag while an existing drag
   * operation is still executing
   */

  public void startDrag(DragGestureEvent trigger,
      Cursor dragCursor,
      Transferable transferable,
      DragSourceListener dsl,
      FlavorMap flavorMap) throws InvalidDnDOperationException {
    startDrag(trigger, dragCursor, null, null, transferable, dsl, flavorMap);
  }

  /**
   * Start a drag, given the <code>DragGestureEvent</code>
   * that initiated the drag, the initial <code>Cursor</code>
   * to use,
   * the <code>Image</code> to drag,
   * the offset of the <code>Image</code> origin
   * from the hotspot of the <code>Cursor</code>
   * at the instant of the trigger,
   * the subject data of the drag, and
   * the <code>DragSourceListener</code>.
   * <P>
   *
   * @param trigger the <code>DragGestureEvent</code> that initiated the drag
   * @param dragCursor the initial {@code Cursor} for this drag operation or {@code null} for the
   * default cursor handling; see <a href="DragSourceContext.html#defaultCursor">DragSourceContext</a>
   * for more details on the cursor handling mechanism during drag and drop
   * @param dragImage the <code>Image</code> to drag or <code>null</code>
   * @param dragOffset the offset of the <code>Image</code> origin from the hotspot of the
   * <code>Cursor</code> at the instant of the trigger
   * @param transferable the subject data of the drag
   * @param dsl the <code>DragSourceListener</code> <P>
   * @throws java.awt.dnd.InvalidDnDOperationException if the Drag and Drop system is unable to
   * initiate a drag operation, or if the user attempts to start a drag while an existing drag
   * operation is still executing
   */

  public void startDrag(DragGestureEvent trigger,
      Cursor dragCursor,
      Image dragImage,
      Point dragOffset,
      Transferable transferable,
      DragSourceListener dsl) throws InvalidDnDOperationException {
    startDrag(trigger, dragCursor, dragImage, dragOffset, transferable, dsl, null);
  }

  /**
   * Start a drag, given the <code>DragGestureEvent</code>
   * that initiated the drag, the initial
   * <code>Cursor</code> to
   * use,
   * the <code>Transferable</code> subject data
   * of the drag, and the <code>DragSourceListener</code>.
   * <P>
   *
   * @param trigger the <code>DragGestureEvent</code> that initiated the drag
   * @param dragCursor the initial {@code Cursor} for this drag operation or {@code null} for the
   * default cursor handling; see <a href="DragSourceContext.html#defaultCursor">DragSourceContext</a>
   * class for more details on the cursor handling mechanism during drag and drop
   * @param transferable the subject data of the drag
   * @param dsl the <code>DragSourceListener</code> <P>
   * @throws java.awt.dnd.InvalidDnDOperationException if the Drag and Drop system is unable to
   * initiate a drag operation, or if the user attempts to start a drag while an existing drag
   * operation is still executing
   */

  public void startDrag(DragGestureEvent trigger,
      Cursor dragCursor,
      Transferable transferable,
      DragSourceListener dsl) throws InvalidDnDOperationException {
    startDrag(trigger, dragCursor, null, null, transferable, dsl, null);
  }

  /**
   * Creates the {@code DragSourceContext} to handle the current drag
   * operation.
   * <p>
   * To incorporate a new <code>DragSourceContext</code>
   * subclass, subclass <code>DragSource</code> and
   * override this method.
   * <p>
   * If <code>dragImage</code> is <code>null</code>, no image is used
   * to represent the drag over feedback for this drag operation, but
   * <code>NullPointerException</code> is not thrown.
   * <p>
   * If <code>dsl</code> is <code>null</code>, no drag source listener
   * is registered with the created <code>DragSourceContext</code>,
   * but <code>NullPointerException</code> is not thrown.
   *
   * @param dscp The <code>DragSourceContextPeer</code> for this drag
   * @param dgl The <code>DragGestureEvent</code> that triggered the drag
   * @param dragCursor The initial {@code Cursor} for this drag operation or {@code null} for the
   * default cursor handling; see <a href="DragSourceContext.html#defaultCursor">DragSourceContext</a>
   * class for more details on the cursor handling mechanism during drag and drop
   * @param dragImage The <code>Image</code> to drag or <code>null</code>
   * @param imageOffset The offset of the <code>Image</code> origin from the hotspot of the cursor
   * at the instant of the trigger
   * @param t The subject data of the drag
   * @param dsl The <code>DragSourceListener</code>
   * @return the <code>DragSourceContext</code>
   * @throws NullPointerException if <code>dscp</code> is <code>null</code>
   * @throws NullPointerException if <code>dgl</code> is <code>null</code>
   * @throws NullPointerException if <code>dragImage</code> is not <code>null</code> and
   * <code>imageOffset</code> is <code>null</code>
   * @throws NullPointerException if <code>t</code> is <code>null</code>
   * @throws IllegalArgumentException if the <code>Component</code> associated with the trigger
   * event is <code>null</code>.
   * @throws IllegalArgumentException if the <code>DragSource</code> for the trigger event is
   * <code>null</code>.
   * @throws IllegalArgumentException if the drag action for the trigger event is
   * <code>DnDConstants.ACTION_NONE</code>.
   * @throws IllegalArgumentException if the source actions for the <code>DragGestureRecognizer</code>
   * associated with the trigger event are equal to <code>DnDConstants.ACTION_NONE</code>.
   */

  protected DragSourceContext createDragSourceContext(DragSourceContextPeer dscp,
      DragGestureEvent dgl, Cursor dragCursor, Image dragImage, Point imageOffset, Transferable t,
      DragSourceListener dsl) {
    return new DragSourceContext(dscp, dgl, dragCursor, dragImage, imageOffset, t, dsl);
  }

  /**
   * This method returns the
   * <code>FlavorMap</code> for this <code>DragSource</code>.
   * <P>
   *
   * @return the <code>FlavorMap</code> for this <code>DragSource</code>
   */

  public FlavorMap getFlavorMap() {
    return flavorMap;
  }

  /**
   * Creates a new <code>DragGestureRecognizer</code>
   * that implements the specified
   * abstract subclass of
   * <code>DragGestureRecognizer</code>, and
   * sets the specified <code>Component</code>
   * and <code>DragGestureListener</code> on
   * the newly created object.
   * <P>
   *
   * @param recognizerAbstractClass the requested abstract type
   * @param actions the permitted source drag actions
   * @param c the <code>Component</code> target
   * @param dgl the <code>DragGestureListener</code> to notify <P>
   * @return the new <code>DragGestureRecognizer</code> or <code>null</code> if the
   * <code>Toolkit.createDragGestureRecognizer</code> method has no implementation available for the
   * requested <code>DragGestureRecognizer</code> subclass and returns <code>null</code>
   */

  public <T extends DragGestureRecognizer> T
  createDragGestureRecognizer(Class<T> recognizerAbstractClass,
      Component c, int actions,
      DragGestureListener dgl) {
    return Toolkit.getDefaultToolkit()
        .createDragGestureRecognizer(recognizerAbstractClass, this, c, actions, dgl);
  }


  /**
   * Creates a new <code>DragGestureRecognizer</code>
   * that implements the default
   * abstract subclass of <code>DragGestureRecognizer</code>
   * for this <code>DragSource</code>,
   * and sets the specified <code>Component</code>
   * and <code>DragGestureListener</code> on the
   * newly created object.
   *
   * For this <code>DragSource</code>
   * the default is <code>MouseDragGestureRecognizer</code>.
   * <P>
   *
   * @param c the <code>Component</code> target for the recognizer
   * @param actions the permitted source actions
   * @param dgl the <code>DragGestureListener</code> to notify <P>
   * @return the new <code>DragGestureRecognizer</code> or <code>null</code> if the
   * <code>Toolkit.createDragGestureRecognizer</code> method has no implementation available for the
   * requested <code>DragGestureRecognizer</code> subclass and returns <code>null</code>
   */

  public DragGestureRecognizer createDefaultDragGestureRecognizer(Component c, int actions,
      DragGestureListener dgl) {
    return Toolkit.getDefaultToolkit()
        .createDragGestureRecognizer(MouseDragGestureRecognizer.class, this, c, actions, dgl);
  }

  /**
   * Adds the specified <code>DragSourceListener</code> to this
   * <code>DragSource</code> to receive drag source events during drag
   * operations intiated with this <code>DragSource</code>.
   * If a <code>null</code> listener is specified, no action is taken and no
   * exception is thrown.
   *
   * @param dsl the <code>DragSourceListener</code> to add
   * @see #removeDragSourceListener
   * @see #getDragSourceListeners
   * @since 1.4
   */
  public void addDragSourceListener(DragSourceListener dsl) {
    if (dsl != null) {
      synchronized (this) {
        listener = DnDEventMulticaster.add(listener, dsl);
      }
    }
  }

  /**
   * Removes the specified <code>DragSourceListener</code> from this
   * <code>DragSource</code>.
   * If a <code>null</code> listener is specified, no action is taken and no
   * exception is thrown.
   * If the listener specified by the argument was not previously added to
   * this <code>DragSource</code>, no action is taken and no exception
   * is thrown.
   *
   * @param dsl the <code>DragSourceListener</code> to remove
   * @see #addDragSourceListener
   * @see #getDragSourceListeners
   * @since 1.4
   */
  public void removeDragSourceListener(DragSourceListener dsl) {
    if (dsl != null) {
      synchronized (this) {
        listener = DnDEventMulticaster.remove(listener, dsl);
      }
    }
  }

  /**
   * Gets all the <code>DragSourceListener</code>s
   * registered with this <code>DragSource</code>.
   *
   * @return all of this <code>DragSource</code>'s <code>DragSourceListener</code>s or an empty
   * array if no such listeners are currently registered
   * @see #addDragSourceListener
   * @see #removeDragSourceListener
   * @since 1.4
   */
  public DragSourceListener[] getDragSourceListeners() {
    return getListeners(DragSourceListener.class);
  }

  /**
   * Adds the specified <code>DragSourceMotionListener</code> to this
   * <code>DragSource</code> to receive drag motion events during drag
   * operations intiated with this <code>DragSource</code>.
   * If a <code>null</code> listener is specified, no action is taken and no
   * exception is thrown.
   *
   * @param dsml the <code>DragSourceMotionListener</code> to add
   * @see #removeDragSourceMotionListener
   * @see #getDragSourceMotionListeners
   * @since 1.4
   */
  public void addDragSourceMotionListener(DragSourceMotionListener dsml) {
    if (dsml != null) {
      synchronized (this) {
        motionListener = DnDEventMulticaster.add(motionListener, dsml);
      }
    }
  }

  /**
   * Removes the specified <code>DragSourceMotionListener</code> from this
   * <code>DragSource</code>.
   * If a <code>null</code> listener is specified, no action is taken and no
   * exception is thrown.
   * If the listener specified by the argument was not previously added to
   * this <code>DragSource</code>, no action is taken and no exception
   * is thrown.
   *
   * @param dsml the <code>DragSourceMotionListener</code> to remove
   * @see #addDragSourceMotionListener
   * @see #getDragSourceMotionListeners
   * @since 1.4
   */
  public void removeDragSourceMotionListener(DragSourceMotionListener dsml) {
    if (dsml != null) {
      synchronized (this) {
        motionListener = DnDEventMulticaster.remove(motionListener, dsml);
      }
    }
  }

  /**
   * Gets all of the  <code>DragSourceMotionListener</code>s
   * registered with this <code>DragSource</code>.
   *
   * @return all of this <code>DragSource</code>'s <code>DragSourceMotionListener</code>s or an
   * empty array if no such listeners are currently registered
   * @see #addDragSourceMotionListener
   * @see #removeDragSourceMotionListener
   * @since 1.4
   */
  public DragSourceMotionListener[] getDragSourceMotionListeners() {
    return getListeners(DragSourceMotionListener.class);
  }

  /**
   * Gets all the objects currently registered as
   * <code><em>Foo</em>Listener</code>s upon this <code>DragSource</code>.
   * <code><em>Foo</em>Listener</code>s are registered using the
   * <code>add<em>Foo</em>Listener</code> method.
   *
   * @param listenerType the type of listeners requested; this parameter should specify an interface
   * that descends from <code>java.util.EventListener</code>
   * @return an array of all objects registered as <code><em>Foo</em>Listener</code>s on this
   * <code>DragSource</code>, or an empty array if no such listeners have been added
   * @throws ClassCastException if <code>listenerType</code> doesn't specify a class or interface
   * that implements <code>java.util.EventListener</code>
   * @see #getDragSourceListeners
   * @see #getDragSourceMotionListeners
   * @since 1.4
   */
  public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
    EventListener l = null;
    if (listenerType == DragSourceListener.class) {
      l = listener;
    } else if (listenerType == DragSourceMotionListener.class) {
      l = motionListener;
    }
    return DnDEventMulticaster.getListeners(l, listenerType);
  }

  /**
   * This method calls <code>dragEnter</code> on the
   * <code>DragSourceListener</code>s registered with this
   * <code>DragSource</code>, and passes them the specified
   * <code>DragSourceDragEvent</code>.
   *
   * @param dsde the <code>DragSourceDragEvent</code>
   */
  void processDragEnter(DragSourceDragEvent dsde) {
    DragSourceListener dsl = listener;
    if (dsl != null) {
      dsl.dragEnter(dsde);
    }
  }

  /**
   * This method calls <code>dragOver</code> on the
   * <code>DragSourceListener</code>s registered with this
   * <code>DragSource</code>, and passes them the specified
   * <code>DragSourceDragEvent</code>.
   *
   * @param dsde the <code>DragSourceDragEvent</code>
   */
  void processDragOver(DragSourceDragEvent dsde) {
    DragSourceListener dsl = listener;
    if (dsl != null) {
      dsl.dragOver(dsde);
    }
  }

  /**
   * This method calls <code>dropActionChanged</code> on the
   * <code>DragSourceListener</code>s registered with this
   * <code>DragSource</code>, and passes them the specified
   * <code>DragSourceDragEvent</code>.
   *
   * @param dsde the <code>DragSourceDragEvent</code>
   */
  void processDropActionChanged(DragSourceDragEvent dsde) {
    DragSourceListener dsl = listener;
    if (dsl != null) {
      dsl.dropActionChanged(dsde);
    }
  }

  /**
   * This method calls <code>dragExit</code> on the
   * <code>DragSourceListener</code>s registered with this
   * <code>DragSource</code>, and passes them the specified
   * <code>DragSourceEvent</code>.
   *
   * @param dse the <code>DragSourceEvent</code>
   */
  void processDragExit(DragSourceEvent dse) {
    DragSourceListener dsl = listener;
    if (dsl != null) {
      dsl.dragExit(dse);
    }
  }

  /**
   * This method calls <code>dragDropEnd</code> on the
   * <code>DragSourceListener</code>s registered with this
   * <code>DragSource</code>, and passes them the specified
   * <code>DragSourceDropEvent</code>.
   *
   * @param dsde the <code>DragSourceEvent</code>
   */
  void processDragDropEnd(DragSourceDropEvent dsde) {
    DragSourceListener dsl = listener;
    if (dsl != null) {
      dsl.dragDropEnd(dsde);
    }
  }

  /**
   * This method calls <code>dragMouseMoved</code> on the
   * <code>DragSourceMotionListener</code>s registered with this
   * <code>DragSource</code>, and passes them the specified
   * <code>DragSourceDragEvent</code>.
   *
   * @param dsde the <code>DragSourceEvent</code>
   */
  void processDragMouseMoved(DragSourceDragEvent dsde) {
    DragSourceMotionListener dsml = motionListener;
    if (dsml != null) {
      dsml.dragMouseMoved(dsde);
    }
  }

  /**
   * Serializes this <code>DragSource</code>. This method first performs
   * default serialization. Next, it writes out this object's
   * <code>FlavorMap</code> if and only if it can be serialized. If not,
   * <code>null</code> is written instead. Next, it writes out
   * <code>Serializable</code> listeners registered with this
   * object. Listeners are written in a <code>null</code>-terminated sequence
   * of 0 or more pairs. The pair consists of a <code>String</code> and an
   * <code>Object</code>; the <code>String</code> indicates the type of the
   * <code>Object</code> and is one of the following:
   * <ul>
   * <li><code>dragSourceListenerK</code> indicating a
   * <code>DragSourceListener</code> object;
   * <li><code>dragSourceMotionListenerK</code> indicating a
   * <code>DragSourceMotionListener</code> object.
   * </ul>
   *
   * @serialData Either a <code>FlavorMap</code> instance, or <code>null</code>, followed by a
   * <code>null</code>-terminated sequence of 0 or more pairs; the pair consists of a
   * <code>String</code> and an <code>Object</code>; the <code>String</code> indicates the type of
   * the <code>Object</code> and is one of the following: <ul> <li><code>dragSourceListenerK</code>
   * indicating a <code>DragSourceListener</code> object; <li><code>dragSourceMotionListenerK</code>
   * indicating a <code>DragSourceMotionListener</code> object. </ul>.
   * @since 1.4
   */
  private void writeObject(ObjectOutputStream s) throws IOException {
    s.defaultWriteObject();

    s.writeObject(SerializationTester.test(flavorMap) ? flavorMap : null);

    DnDEventMulticaster.save(s, dragSourceListenerK, listener);
    DnDEventMulticaster.save(s, dragSourceMotionListenerK, motionListener);
    s.writeObject(null);
  }

  /**
   * Deserializes this <code>DragSource</code>. This method first performs
   * default deserialization. Next, this object's <code>FlavorMap</code> is
   * deserialized by using the next object in the stream.
   * If the resulting <code>FlavorMap</code> is <code>null</code>, this
   * object's <code>FlavorMap</code> is set to the default FlavorMap for
   * this thread's <code>ClassLoader</code>.
   * Next, this object's listeners are deserialized by reading a
   * <code>null</code>-terminated sequence of 0 or more key/value pairs
   * from the stream:
   * <ul>
   * <li>If a key object is a <code>String</code> equal to
   * <code>dragSourceListenerK</code>, a <code>DragSourceListener</code> is
   * deserialized using the corresponding value object and added to this
   * <code>DragSource</code>.
   * <li>If a key object is a <code>String</code> equal to
   * <code>dragSourceMotionListenerK</code>, a
   * <code>DragSourceMotionListener</code> is deserialized using the
   * corresponding value object and added to this <code>DragSource</code>.
   * <li>Otherwise, the key/value pair is skipped.
   * </ul>
   *
   * @see java.awt.datatransfer.SystemFlavorMap#getDefaultFlavorMap
   * @since 1.4
   */
  private void readObject(ObjectInputStream s)
      throws ClassNotFoundException, IOException {
    s.defaultReadObject();

    // 'flavorMap' was written explicitly
    flavorMap = (FlavorMap) s.readObject();

    // Implementation assumes 'flavorMap' is never null.
    if (flavorMap == null) {
      flavorMap = SystemFlavorMap.getDefaultFlavorMap();
    }

    Object keyOrNull;
    while (null != (keyOrNull = s.readObject())) {
      String key = ((String) keyOrNull).intern();

      if (dragSourceListenerK == key) {
        addDragSourceListener((DragSourceListener) (s.readObject()));
      } else if (dragSourceMotionListenerK == key) {
        addDragSourceMotionListener(
            (DragSourceMotionListener) (s.readObject()));
      } else {
        // skip value for unrecognized key
        s.readObject();
      }
    }
  }

  /**
   * Returns the drag gesture motion threshold. The drag gesture motion threshold
   * defines the recommended behavior for {@link MouseDragGestureRecognizer}s.
   * <p>
   * If the system property <code>awt.dnd.drag.threshold</code> is set to
   * a positive integer, this method returns the value of the system property;
   * otherwise if a pertinent desktop property is available and supported by
   * the implementation of the Java platform, this method returns the value of
   * that property; otherwise this method returns some default value.
   * The pertinent desktop property can be queried using
   * <code>java.awt.Toolkit.getDesktopProperty("DnD.gestureMotionThreshold")</code>.
   *
   * @return the drag gesture motion threshold
   * @see MouseDragGestureRecognizer
   * @since 1.5
   */
  public static int getDragThreshold() {
    int ts = AccessController.doPrivileged(
        new GetIntegerAction("awt.dnd.drag.threshold", 0)).intValue();
    if (ts > 0) {
      return ts;
    } else {
      Integer td = (Integer) Toolkit.getDefaultToolkit().
          getDesktopProperty("DnD.gestureMotionThreshold");
      if (td != null) {
        return td.intValue();
      }
    }
    return 5;
  }

    /*
     * fields
     */

  private transient FlavorMap flavorMap = SystemFlavorMap.getDefaultFlavorMap();

  private transient DragSourceListener listener;

  private transient DragSourceMotionListener motionListener;
}
